/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"

#include "../vsfip_priv.h"

enum vsfip_netif_EVT_t
{
	VSFIP_NETIF_EVT_OUTPUT			= VSFSM_EVT_USER_LOCAL + 0,
	VSFIP_NETIF_EVT_OUTPUTTED		= VSFSM_EVT_USER_LOCAL + 1,

	VSFIP_NETIF_EVT_ARPS_REQUEST	= VSFSM_EVT_USER_LOCAL + 2,
	VSFIP_NETIF_EVT_ARPS_REPLIED	= VSFSM_EVT_USER_LOCAL + 3,
	VSFIP_NETIF_EVT_ARPC_REQUEST	= VSFSM_EVT_USER_LOCAL + 4,

	VSFIP_NETIF_EVT_ARPC_TIMEOUT = VSFSM_EVT_USER_LOCAL_INSTANT + 0,
	VSFIP_NETIF_EVT_ARPC_UPDATE	= VSFSM_EVT_USER_LOCAL_INSTANT + 1,
	VSFIP_NETIF_EVT_ARPC_REQOUT = VSFSM_EVT_USER_LOCAL_INSTANT + 2,
};

enum vsfip_netif_ARP_op_t
{
	ARP_REQUEST = 1,
	ARP_REPLY = 2,
	RARP_REQUEST = 3,
	RARP_REPLY = 4,
};

// ARP
PACKED_HEAD struct PACKED_MID vsfip_arphead_t
{
	uint16_t hwtype;
	uint16_t prototype;
	uint8_t hwlen;
	uint8_t protolen;
	uint16_t op;
}; PACKED_TAIL

// netdrv adatpter
static vsf_err_t vsfip_netdrv_adapter_on_connect(void *netif)
{
	return vsfip_netif_add((struct vsfip_netif_t *)netif);
}

static void vsfip_netdrv_adapter_on_disconnect(void *netif)
{
	vsfip_netif_remove((struct vsfip_netif_t *)netif);
}

static void vsfip_netdrv_adapter_on_outputted(void *netif, void *netbuf, vsf_err_t err)
{
	struct vsfip_netif_t *vsfip_netif = (struct vsfip_netif_t *)netif;
	struct vsfip_buffer_t *vsfip_netbuf = (struct vsfip_buffer_t *)netbuf;

	vsfsm_post_evt(&vsfip_netif->output_sm, VSFIP_NETIF_EVT_OUTPUTTED);
	vsfip_netbuf->on_finish(vsfip_netbuf, err);
}

static void vsfip_netdrv_adapter_on_inputted(void *netif, void *netbuf, uint32_t len)
{
	struct vsfip_netif_t *vsfip_netif = (struct vsfip_netif_t *)netif;
	struct vsfip_buffer_t *vsfip_netbuf = (struct vsfip_buffer_t *)netbuf;

	vsfip_netbuf->buf.size = len;
	vsfip_buffer_set_netif(vsfip_netbuf, vsfip_netif);
	vsfip_netif->op->input(vsfip_netbuf);
}

static struct vsfsm_state_t *
vsfip_netif_output_evthandler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	struct vsfip_netif_t *netif = (struct vsfip_netif_t *)sm->user_data;
	struct vsfq_node_t *node;
	struct vsfip_buffer_t *tmpbuf;

	switch (evt)
	{
	case VSFIP_NETIF_EVT_OUTPUTTED:
	case VSFSM_EVT_INIT:
		if (vsfsm_sem_pend(&netif->output_sem, sm))
			break;
	case VSFIP_NETIF_EVT_OUTPUT:
		node = vsfq_dequeue(&netif->outq);
		tmpbuf = container_of(node, struct vsfip_buffer_t, netif_node);
		netif->op->output(tmpbuf);
		break;
	}
	return NULL;
}

static uint8_t *vsfip_netdrv_adapter_header(void *netbuf, int32_t len)
{
	struct vsfip_buffer_t *vsfip_netbuf = (struct vsfip_buffer_t *)netbuf;
	if (len > 0)
	{
		if (vsfip_buffer_header(vsfip_netbuf, len))
			return NULL;
		return vsfip_netbuf->buf.buffer;
	}
	else
	{
		len = -len;
		if (vsfip_netbuf->buf.size < len)
			return NULL;
		vsfip_netbuf->buf.buffer += len;
		vsfip_netbuf->buf.size -= len;
		return vsfip_netbuf->buf.buffer;
	}
}

static void *vsfip_netdrv_adapter_alloc_buf(struct vsf_buffer_t *buf)
{
	struct vsfip_buffer_t *netbuf = vsfip_buffer_get(buf->size);
	if (netbuf != NULL)
		*buf = netbuf->buf;
	return (void *)netbuf;
}

static void vsfip_netdrv_adapter_free_buf(void *netbuf)
{
	struct vsfip_buffer_t *vsfip_netbuf = (struct vsfip_buffer_t *)netbuf;
	vsfip_buffer_release(vsfip_netbuf);
}

static const struct vsfip_netdrv_adapter_t vsfip_netdrv_adapter =
{
	.netif_size		= sizeof(struct vsfip_netif_t),

	.on_connect		= vsfip_netdrv_adapter_on_connect,
	.on_disconnect	= vsfip_netdrv_adapter_on_disconnect,

	.alloc_buf		= vsfip_netdrv_adapter_alloc_buf,
	.free_buf		= vsfip_netdrv_adapter_free_buf,

	.header			= vsfip_netdrv_adapter_header,
	.on_outputted	= vsfip_netdrv_adapter_on_outputted,
	.on_inputted	= vsfip_netdrv_adapter_on_inputted,
};

// netif
static void vsfip_netif_destruct(struct vsfip_netif_t *netif)
{
	vsfsm_fini(&netif->arpc.sm);
//	vsfsm_sem_fini(&netif->arpc.sem);
//	vsfsm_sem_fini(&netif->output_sem);
//	vsfq_fini(&netif->outq);
}

static vsf_err_t vsfip_netif_arp_client_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt);
static vsf_err_t vsfip_netif_construct(struct vsfip_netif_t *netif)
{
	vsfq_init(&netif->outq);
	netif->arpc.buf = NULL;
	netif->arp_time = 1;

	vsfsm_sem_init(&netif->output_sem, 0, VSFIP_NETIF_EVT_OUTPUT);
	vsfsm_sem_init(&netif->arpc.sem, 0, VSFIP_NETIF_EVT_ARPC_REQUEST);

	netif->arpc.pt.user_data = netif;
	netif->arpc.pt.thread = vsfip_netif_arp_client_thread;
	return vsfsm_pt_init(&netif->arpc.sm, &netif->arpc.pt);
}

void vsfip_netif_set_netdrv(struct vsfip_netif_t *netif, struct vsfip_netdrv_t *netdrv)
{
	netif->netdrv = netdrv;
	netdrv->netif = netif;
	netdrv->adapter = (struct vsfip_netdrv_adapter_t *)&vsfip_netdrv_adapter;
}

vsf_err_t vsfip_netif_init(struct vsfip_netif_t *netif)
{
	vsf_err_t err = vsfip_netdrv_init(netif->netdrv);
	if (!err)
	{
		netif->output_sm.init_state.evt_handler = vsfip_netif_output_evthandler;
		netif->output_sm.user_data = netif;
		vsfsm_init(&netif->output_sm);
	}
	return err;
}

void vsfip_netif_fini(struct vsfip_netif_t *netif)
{
	vsfip_netdrv_fini(netif->netdrv);
	vsfsm_fini(&netif->output_sm);
}

// first call to vsfip_netif_reference is at vsfip_netif_add,
//	which is called protected from the netdrv,
//	so vsfip_netif_construct is called protected
void vsfip_netif_reference(struct vsfip_netif_t *netif)
{
	if (!netif->ref++)
	{
		vsfip_netif_construct(netif);
	}
#ifdef VSFIP_TRACE_NETIF
	vsfdbg_printf(": %d" VSFCFG_DEBUG_LINEEND, netif->ref);
#endif
}

void vsfip_netif_release(struct vsfip_netif_t *netif)
{
#ifdef VSFIP_TRACE_NETIF
	netif->ref--;
	vsfdbg_printf(": %d" VSFCFG_DEBUG_LINEEND, netif->ref);
	if (!netif->ref)
#else
	if (!--netif->ref)
#endif
	{
		vsfip_netif_destruct(netif);
		vsfip_netdrv_free(netif->netdrv);
		netif->netdrv = NULL;
	}
}

static void vsfip_netif_get_mac_broadcast(struct vsfip_netif_t *netif,
		struct vsfip_macaddr_t *macaddr)
{
	macaddr->size = netif->netdrv->macaddr.size;
	memset(macaddr->addr.s_addr_buf, 0, sizeof(macaddr->addr.s_addr_buf));
	memset(macaddr->addr.s_addr_buf, 0xFF, macaddr->size);
}

static bool vsfip_netif_get_mac(struct vsfip_netif_t *netif,
		struct vsfip_ipaddr_t *ip, struct vsfip_macaddr_t *mac)
{
	uint32_t i;
	if (0xFFFFFFFF == ip->addr.s_addr)
	{
		vsfip_netif_get_mac_broadcast(netif, mac);
		return true;
	}
	mac->size = 0;
	for (i = 0; i < VSFIP_CFG_ARPCACHE_SIZE; i++)
	{
		if (netif->arp_cache[i].time &&
			(ip->addr.s_addr == netif->arp_cache[i].assoc.ip.addr.s_addr))
		{
			*mac = netif->arp_cache[i].assoc.mac;
			return true;
		}
	}
	return false;
}

static vsf_err_t vsfip_netif_output(struct vsfip_buffer_t *buf,
		enum vsfip_netif_proto_t proto, struct vsfip_macaddr_t *mac, bool urgent)
{
	struct vsfip_netif_t *netif = buf->netif;

	if (netif->op->header(buf, proto, mac))
	{
		vsfip_buffer_release(buf);
		return VSFERR_FAIL;
	}

	uint8_t origlevel = vsfsm_sched_lock();
	if (urgent)
	{
		buf->netif_node.next = netif->outq.head;
		netif->outq.head = &buf->netif_node;
	}
	else
		vsfq_append(&netif->outq, &buf->netif_node);
	vsfsm_sched_unlock(origlevel);
	return vsfsm_sem_post(&netif->output_sem);
}

static void vsfip_netif_get_ipaddr(struct vsfip_buffer_t *buf,
		struct vsfip_ipaddr_t *ipaddr)
{
	uint8_t ipver = *buf->iphead.ipver >> 4;
	if (4 == ipver)
	{
		// IPv4
		ipaddr->size = 4;
		ipaddr->addr.s_addr = buf->iphead.ip4head->ipdest;
	}
	else //if (6 == ipver)
	{
		// IPv6
		ipaddr->size = 6;
	}
}

static bool vsfip_netif_islocal(struct vsfip_netif_t *netif,
		struct vsfip_ipaddr_t *ipaddr)
{
	int i;
	uint8_t addr_mask;

	if (0xFFFFFFFF == ipaddr->addr.s_addr)
	{
		return true;
	}
	for (i = 0; i < netif->netmask.size; i++)
	{
		addr_mask = netif->netmask.addr.s_addr_buf[i];
		if ((ipaddr->addr.s_addr_buf[i] & addr_mask) !=
			(netif->gateway.addr.s_addr_buf[i] & addr_mask))
		{
			return false;
		}
	}
	return true;
}

vsf_err_t vsfip_netif_ip_output(struct vsfip_buffer_t *buf, bool urgent)
{
	struct vsfip_netif_t *netif = buf->netif;
	struct vsfip_macaddr_t mac;
	struct vsfip_ipaddr_t dest, *ip_for_mac;

	vsfip_netif_get_ipaddr(buf, &dest);
	ip_for_mac = vsfip_netif_islocal(netif, &dest) || !netif->gateway.size ?
					&dest : &netif->gateway;
	if (!vsfip_netif_get_mac(netif, ip_for_mac, &mac) && netif->netdrv->macaddr.size)
	{
		uint8_t origlevel = vsfsm_sched_lock();
		vsfq_append(&netif->arpc.requestq, &buf->netif_node);
		vsfsm_sched_unlock(origlevel);
		return vsfsm_sem_post(&netif->arpc.sem);
	}
	else if (buf->buf.size)
	{
		return vsfip_netif_output(buf, VSFIP_NETIF_PROTO_IP, &mac, urgent);
	}
	return VSFERR_NONE;
}

void vsfip_netif_ip4_input(struct vsfip_buffer_t *buf)
{
	vsfip_ip4_input(buf);
}

void vsfip_netif_ip6_input(struct vsfip_buffer_t *buf)
{
	vsfip_ip6_input(buf);
}

void vsfip_netif_arp_add_assoc(struct vsfip_netif_t *netif,
		uint8_t hwlen, uint8_t *hwaddr, uint8_t protolen, uint8_t *protoaddr)
{
	struct vsfip_arp_entry_t *entry = &netif->arp_cache[0];
	struct vsfip_ipaddr_t ip;
	struct vsfip_macaddr_t mac;
	uint32_t i;

	ip.size = protolen;
	memcpy(ip.addr.s_addr_buf, protoaddr, protolen);
	if (vsfip_netif_get_mac(netif, &ip, &mac))
		return;

	for (i = 0; i < VSFIP_CFG_ARPCACHE_SIZE; i++)
	{
		if (0 == netif->arp_cache[i].time)
		{
			entry = &netif->arp_cache[i];
			break;
		}
		if (netif->arp_cache[i].time < entry->time)
		{
			entry = &netif->arp_cache[i];
		}
	}
	entry->assoc.mac.size = hwlen;
	memcpy(entry->assoc.mac.addr.s_addr_buf, hwaddr, hwlen);
	entry->assoc.ip.size = protolen;
	memcpy(entry->assoc.ip.addr.s_addr_buf, protoaddr, protolen);
	entry->time = netif->arp_time++;
}

static void vsfip_netif_on_finish(struct vsfip_buffer_t *buf, vsf_err_t err)
{
	vsfip_buffer_release(buf);
}

void vsfip_netif_arp_input(struct vsfip_buffer_t *buf)
{
	struct vsfip_netif_t *netif = buf->netif;
	struct vsfip_arphead_t *head = (struct vsfip_arphead_t *)buf->buf.buffer;
	uint8_t *ptr = (uint8_t *)head + sizeof(struct vsfip_arphead_t);
	uint8_t *bufptr;

	// endian fix
	head->hwtype = BE_TO_SYS_U16(head->hwtype);
	head->prototype = BE_TO_SYS_U16(head->prototype);
	head->op = BE_TO_SYS_U16(head->op);

	switch (head->op)
	{
	case ARP_REQUEST:
		bufptr = (uint8_t *)head + sizeof(struct vsfip_arphead_t);

		if ((head->hwlen == netif->netdrv->macaddr.size) &&
			(head->protolen == netif->ipaddr.size) &&
			(buf->buf.size >= (sizeof(struct vsfip_arphead_t) +
								2 * (head->hwlen + head->protolen))) &&
			!memcmp(bufptr + 2 * head->hwlen + head->protolen,
					netif->ipaddr.addr.s_addr_buf, head->protolen))
		{
			struct vsfip_macaddr_t macaddr;
			struct vsfip_ipaddr_t ipaddr;

			// process the ARP request
			head->hwtype = SYS_TO_BE_U16(netif->netdrv->hwtype);
			head->prototype = SYS_TO_BE_U16(VSFIP_NETIF_PROTO_IP);
			head->op = SYS_TO_BE_U16(ARP_REPLY);
			macaddr.size = head->hwlen;
			memcpy(macaddr.addr.s_addr_buf, bufptr, head->hwlen);
			memcpy(ipaddr.addr.s_addr_buf, bufptr + head->hwlen,
					head->protolen);
			memcpy(bufptr, netif->netdrv->macaddr.addr.s_addr_buf, head->hwlen);
			memcpy(bufptr + head->hwlen, netif->ipaddr.addr.s_addr_buf,
					head->protolen);
			memcpy(bufptr + head->hwlen + head->protolen,
					macaddr.addr.s_addr_buf, head->hwlen);
			memcpy(bufptr + 2 * head->hwlen + head->protolen,
					ipaddr.addr.s_addr_buf, head->protolen);

			// send ARP reply
			buf->on_finish = vsfip_netif_on_finish;
			vsfip_netif_output(buf, VSFIP_NETIF_PROTO_ARP, &macaddr, false);
			return;
		}
		break;
	case ARP_REPLY:
		// for ARP reply, cache and send UPDATE event to netif->arpc.sm_pending
		if ((head->hwlen != netif->netdrv->macaddr.size) ||
			(head->protolen != netif->ipaddr.size) ||
			(buf->buf.size < (sizeof(struct vsfip_arphead_t) +
								2 * (head->hwlen + head->protolen))))
		{
			break;
		}

		// search a most suitable slot in the ARP cache
		vsfip_netif_arp_add_assoc(netif, head->hwlen, ptr, head->protolen,
									ptr + netif->netdrv->macaddr.size);

		if (netif->arpc.sm_pending != NULL)
		{
			vsfsm_post_evt(netif->arpc.sm_pending, VSFIP_NETIF_EVT_ARPC_UPDATE);
		}
		break;
	}
	vsfip_buffer_release(buf);
}

static struct vsfip_buffer_t *vsfip_netif_prepare_arp_request(
		struct vsfip_netif_t *netif, struct vsfip_ipaddr_t *ipaddr)
{
	struct vsfip_buffer_t *buf = VSFIP_NETIFBUF_GET(128);

	if (buf != NULL)
	{
		struct vsfip_macaddr_t *macaddr = &netif->netdrv->macaddr;
		struct vsfip_arphead_t *head;
		uint8_t *ptr;

		buf->on_finish = vsfip_netif_on_finish;
		vsfip_buffer_set_netif(buf, netif);

		head = (struct vsfip_arphead_t *)buf->buf.buffer;
		head->hwtype = SYS_TO_BE_U16(netif->netdrv->hwtype);
		head->prototype = SYS_TO_BE_U16(VSFIP_NETIF_PROTO_IP);
		head->hwlen = (uint8_t)macaddr->size;
		head->protolen = (uint8_t)netif->ipaddr.size;
		head->op = SYS_TO_BE_U16(ARP_REQUEST);
		ptr = (uint8_t *)head + sizeof(struct vsfip_arphead_t);
		memcpy(ptr, macaddr->addr.s_addr_buf, macaddr->size);
		ptr += macaddr->size;
		memcpy(ptr, netif->ipaddr.addr.s_addr_buf, netif->ipaddr.size);
		ptr += netif->ipaddr.size;
		memset(ptr, 0, macaddr->size);
		ptr += macaddr->size;
		memcpy(ptr, ipaddr->addr.s_addr_buf, ipaddr->size);
		ptr += ipaddr->size;
		buf->app.size = buf->buf.size = ptr - (uint8_t *)head;
	}
	return buf;
}

static vsf_err_t vsfip_netif_arp_client_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfip_netif_t *netif = (struct vsfip_netif_t *)pt->user_data;
	struct vsfq_node_t *node;
	struct vsfip_macaddr_t mac;
	struct vsfip_ipaddr_t dest;

	vsfsm_pt_begin(pt);

	while (!netif->quit)
	{
		if (vsfsm_sem_pend(&netif->arpc.sem, pt->sm))
		{
			vsfsm_pt_wfe(pt, VSFIP_NETIF_EVT_ARPC_REQUEST);
		}

		node = vsfq_dequeue(&netif->arpc.requestq);
		netif->arpc.cur_request =
					container_of(node, struct vsfip_buffer_t, netif_node);
		if (netif->arpc.cur_request != NULL)
		{
			vsfip_netif_get_ipaddr(netif->arpc.cur_request, &dest);
			// for local ip, send ARP for dest ip
			// for non-local ip, if gateway is valid, send to gateway
			// for non-local ip, if gateway is not valid, send ARP for dest ip
			// 		and if proxy ARP is enabled on router, router will reply
			netif->arpc.ip_for_mac =
				vsfip_netif_islocal(netif, &dest) || !netif->gateway.size ?
					dest : netif->gateway;
			if (!vsfip_netif_get_mac(netif, &netif->arpc.ip_for_mac, &mac))
			{
				netif->arpc.retry = VSFIP_CFG_ARP_RETRY;
			retry:
				netif->arpc.buf = vsfip_netif_prepare_arp_request(netif,
						&netif->arpc.ip_for_mac);
				if (netif->arpc.buf != NULL)
				{
					vsfip_netif_get_mac_broadcast(netif, &mac);
					if (!vsfip_netif_output(netif->arpc.buf,
							VSFIP_NETIF_PROTO_ARP, &mac, false))
					{
						netif->arpc.sm_pending = pt->sm;
						// wait for reply with timeout
						netif->arpc.to.interval = VSFIP_CFG_ARP_TIMEOUT;
						vsfsm_notifier_set_evt(&netif->arpc.to.notifier, pt->sm,
							VSFIP_NETIF_EVT_ARPC_TIMEOUT);
						netif->arpc.to.trigger_cnt = 1;
						vsftimer_enqueue(&netif->arpc.to);

						evt = VSFSM_EVT_NONE;
						vsfsm_pt_entry(pt);
						if ((evt != VSFIP_NETIF_EVT_ARPC_UPDATE) &&
							(evt != VSFIP_NETIF_EVT_ARPC_TIMEOUT))
						{
							return VSFERR_NOT_READY;
						}
						vsftimer_dequeue(&netif->arpc.to);
						netif->arpc.sm_pending = NULL;
					}

					if (!vsfip_netif_get_mac(netif, &netif->arpc.ip_for_mac, &mac) &&
						(netif->arpc.retry > 0))
					{
						netif->arpc.retry--;
						goto retry;
					}
				}
			}

			if (!mac.size)
			{
				vsfip_buffer_release(netif->arpc.cur_request);
			}
			else
			{
				vsfip_netif_output(netif->arpc.cur_request,
						VSFIP_NETIF_PROTO_IP, &mac, false);
			}
		}
	}

	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}
